import torch  # 导入torch库
import torch.nn as nn  # 导入torch.nn库并重命名为nn
from torch.nn import GELU
import tiktoken
from torch.utils.data import Dataset, DataLoader  # 从torch.utils.data导入Dataset和DataLoader
import matplotlib.pyplot as plt  # 导入matplotlib.pyplot库作为plt
import numpy as np  # 导入numpy库
import math  # 导入数学库
class GPTModel(nn.Module):  # 定义GPTModel类，继承nn.Module
    def __init__(self, cfg):  # 初始化方法，接受配置参数cfg
        super().__init__()  # 调用父类的初始化方法
        self.tok_emb = nn.Embedding(cfg["vocab_size"], cfg["emb_dim"])  # 创建分词嵌入层
        self.pos_emb = nn.Embedding(cfg["context_length"], cfg["emb_dim"])  # 创建位置嵌入层
        self.drop_emb = nn.Dropout(cfg["drop_rate"])  # 创建dropout层
        self.trf_blocks = nn.Sequential(  # 创建一系列Transformer块
            *[TransformerBlock(cfg) for _ in range(cfg["n_layers"])]
        )
        self.final_norm = LayerNorm(cfg["emb_dim"])  # 创建最终层归一化层
        self.out_head = nn.Linear(cfg["emb_dim"], cfg["vocab_size"], bias=False)  # 创建输出层

    def forward(self, in_idx):  # 前向传播方法
        batch_size, seq_len = in_idx.shape  # 获取输入的批量大小和序列长度
        tok_embeds = self.tok_emb(in_idx)  # 获取分词嵌入
        pos_embeds = self.pos_emb(torch.arange(seq_len, device=in_idx.device))  # 获取位置嵌入
        x = tok_embeds + pos_embeds  # 将分词嵌入和位置嵌入相加
        x = self.drop_emb(x)  # 应用dropout
        x = self.trf_blocks(x)  # 通过Transformer块
        x = self.final_norm(x)  # 应用最终层归一化
        logits = self.out_head(x)  # 通过输出层得到logits
        return logits  # 返回logits


class LayerNorm(nn.Module):  # 定义LayerNorm类，继承nn.Module
    def __init__(self, emb_dim):  # 初始化方法
        super().__init__()  # 调用父类的初始化方法
        self.eps = 1e-5  # 设置epsilon值
        self.scale = nn.Parameter(torch.ones(emb_dim))  # 定义可训练的scale参数
        self.shift = nn.Parameter(torch.zeros(emb_dim))  # 定义可训练的shift参数

    def forward(self, x):  # 定义前向传播方法
        mean = x.mean(dim=-1, keepdim=True)  # 计算均值
        var = x.var(dim=-1, keepdim=True, unbiased=False)  # 计算方差
        norm_x = (x - mean) / torch.sqrt(var + self.eps)  # 进行归一化
        return self.scale * norm_x + self.shift  # 返回归一化后的输出


class FeedForward(nn.Module):  # 定义FeedForward类，继承nn.Module
    def __init__(self, cfg):  # 初始化方法
        super().__init__()  # 调用父类的初始化方法
        self.layers = nn.Sequential(  # 使用顺序容器定义网络层
            nn.Linear(cfg["emb_dim"], 4 * cfg["emb_dim"]),  # 线性层1
            GELU(),  # GELU激活函数
            nn.Linear(4 * cfg["emb_dim"], cfg["emb_dim"]),  # 线性层2
        )

    def forward(self, x):  # 定义前向传播方法
        return self.layers(x)  # 返回网络层的输出


class MultiHeadAttention(nn.Module):  # 定义多头注意力类，继承自 nn.Module
    def __init__(self, d_in, d_out,
                 context_length, dropout, num_heads, qkv_bias=False):  # 初始化方法，定义输入、输出、上下文长度、dropout率、头的数量和是否使用偏置
        super().__init__()  # 调用父类的初始化方法
        assert d_out % num_heads == 0, "d_out must be divisible by num_heads"  # 确保输出维度能被头的数量整除
        self.d_out = d_out  # 保存输出维度
        self.num_heads = num_heads  # 保存头的数量
        self.head_dim = d_out // num_heads  # 计算每个头的维度

        self.W_query = nn.Linear(d_in, d_out, bias=qkv_bias)  # 定义查询权重矩阵
        self.W_key = nn.Linear(d_in, d_out, bias=qkv_bias)  # 定义键权重矩阵
        self.W_value = nn.Linear(d_in, d_out, bias=qkv_bias)  # 定义值权重矩阵
        self.out_proj = nn.Linear(d_out, d_out)  # 定义输出投影矩阵
        self.dropout = nn.Dropout(dropout)  # 定义 dropout 层
        self.register_buffer(
            'mask',
            torch.triu(torch.ones(context_length, context_length), diagonal=1)
        )  # 注册一个上三角矩阵作为掩码，用于防止信息泄露

    def forward(self, x):  # 前向传播方法
        b, num_tokens, d_in = x.shape  # 获取批量大小、词元数量和输入维度
        keys = self.W_key(x)  # 计算键
        queries = self.W_query(x)  # 计算查询
        values = self.W_value(x)  # 计算值

        keys = keys.view(b, num_tokens, self.num_heads, self.head_dim)  # 将键张量 reshape
        values = values.view(b, num_tokens, self.num_heads, self.head_dim)  # 将值张量 reshape
        queries = queries.view(b, num_tokens, self.num_heads, self.head_dim)  # 将查询张量 reshape

        keys = keys.transpose(1, 2)  # 转置键张量
        queries = queries.transpose(1, 2)  # 转置查询张量
        values = values.transpose(1, 2)  # 转置值张量

        attn_scores = queries @ keys.transpose(2, 3)  # 计算注意力分数
        mask_bool = self.mask.bool()[:num_tokens, :num_tokens]  # 获取掩码
        attn_scores.masked_fill_(mask_bool, -torch.inf)  # 应用掩码

        attn_weights = torch.softmax(
            attn_scores / keys.shape[-1] ** 0.5, dim=-1)  # 计算注意力权重
        attn_weights = self.dropout(attn_weights)  # 应用 dropout

        context_vec = (attn_weights @ values).transpose(1, 2)  # 计算上下文向量
        context_vec = context_vec.contiguous().view(b, num_tokens, self.d_out)  # 调整上下文向量的形状
        context_vec = self.out_proj(context_vec)  # 应用输出投影
        return context_vec  # 返回上下文向量


class TransformerBlock(nn.Module):  # 定义TransformerBlock类，继承nn.Module
    def __init__(self, cfg):  # 初始化方法
        super().__init__()  # 调用父类的初始化方法
        self.attn = MultiHeadAttention(  # 实例化多头注意力
            d_in=cfg["emb_dim"],
            d_out=cfg["emb_dim"],
            context_length=cfg["context_length"],
            num_heads=cfg["n_heads"],
            dropout=cfg["drop_rate"],
            qkv_bias=cfg["qkv_bias"]
        )
        self.ff = FeedForward(cfg)  # 实例化前馈层
        self.norm1 = LayerNorm(cfg["emb_dim"])  # 实例化层归一化1
        self.norm2 = LayerNorm(cfg["emb_dim"])  # 实例化层归一化2
        self.drop_shortcut = nn.Dropout(cfg["drop_rate"])  # 实例化dropout

    def forward(self, x):  # 定义前向传播方法
        shortcut = x  # 保存输入以便添加shortcut
        x = self.norm1(x)  # 层归一化1
        x = self.attn(x)  # 多头注意力
        x = self.drop_shortcut(x)  # dropout
        x = x + shortcut  # 添加原始输入回去

        shortcut = x  # 保存输入以便添加shortcut
        x = self.norm2(x)  # 层归一化2
        x = self.ff(x)  # 前馈层
        x = self.drop_shortcut(x)  # dropout
        x = x + shortcut  # 添加原始输入回去
        return x  # 返回最终输出


def generate_text_simple(model, idx, max_new_tokens, context_size):  # 定义生成简单文本的函数
    for _ in range(max_new_tokens):  # 循环生成新词元
        idx_cond = idx[:, -context_size:]  # 截取上下文
        with torch.no_grad():  # 禁用梯度计算
            logits = model(idx_cond)  # 通过模型获得logits

        logits = logits[:, -1, :]  # 只关注最后一个时间步的logits
        probas = torch.softmax(logits, dim=-1)  # 应用softmax函数获取概率分布
        idx_next = torch.argmax(probas, dim=-1, keepdim=True)  # 选择具有最高概率的词元ID
        idx = torch.cat([idx, idx_next], dim=1)  # 将生成的词元ID附加到当前上下文
    return idx  # 返回生成的词元序列


def generate(model, idx, max_new_tokens, context_size,  # 定义生成函数
             temperature=1.0, top_k=None, eos_id=None):
    for _ in range(max_new_tokens):  # A 遍历最大新词元数
        idx_cond = idx[-context_size:]  # 获取上下文
        with torch.no_grad():  # 禁用梯度计算
            logits = model(idx_cond)  # 获取模型的logits
            logits = logits[:, -1, :]  # 只关注最后一个时间步的logits
        if top_k is not None:  # B 在新部分中，使用top-k采样
            top_logits, _ = torch.topk(logits, top_k)
            min_val = top_logits[:, -1]
            logits = torch.where(
                logits < min_val,  # A 确定小于top 3最小值的logits
                torch.tensor(float('-inf')).to(logits.device),  # B 将这些较低的logits赋值为-inf
                logits  # C 其他词元保留原始logits
            )

        if temperature > 0.0:  # C 这是我们应用温度缩放的新部分
            logits = logits / temperature  # 对logits进行温度缩放
            probs = torch.softmax(logits, dim=-1)  # 应用softmax函数
            idx_next = torch.multinomial(probs, num_samples=1)  # 使用多项式函数进行采样
        else:
            idx_next = torch.argmax(logits, dim=-1, keepdim=True)  # D 在禁用温度缩放时执行贪婪选择
        if idx_next == eos_id:  # E 如果遇到序列结束词元且指定了eos_id，则提前终止生成
            break
        idx = torch.cat((idx, idx_next), dim=1)  # 将新词元添加到索引序列中
    return idx  # 返回索引序列


def text_to_token_ids(text, tokenizer):  # 定义text_to_token_ids函数
    encoded = tokenizer.encode(text, allowed_special={'<|endoftext|>'})  # 编码文本，允许特殊词元
    encoded_tensor = torch.tensor(encoded).unsqueeze(0)  # 添加批次维度
    return encoded_tensor  # 返回编码后的张量


def token_ids_to_text(token_ids, tokenizer):  # 定义token_ids_to_text函数
    flat = token_ids.squeeze(0)  # 移除批次维度
    return tokenizer.decode(flat.tolist())  # 解码为文本


def calc_loss_batch(input_batch, target_batch, model, device):  # 定义计算批次损失的函数
    input_batch, target_batch = input_batch.to(device), target_batch.to(device)  # 将输入和目标批次转移到设备上
    logits = model(input_batch)  # 模型计算logits
    loss = torch.nn.functional.cross_entropy(  # 计算交叉熵损失
        logits.flatten(0, 1), target_batch.flatten()  # 展平logits和目标批次
    )
    return loss  # 返回损失


def calc_loss_loader(data_loader, model, device, num_batches=None):  # 定义计算加载器损失的函数
    total_loss = 0.  # 初始化总损失为0
    if len(data_loader) == 0:  # 如果加载器为空
        return float("nan")  # 返回NaN
    elif num_batches is None:  # 如果未指定批次数
        num_batches = len(data_loader)  # 使用加载器中的批次数
    else:
        num_batches = min(num_batches, len(data_loader))  # 限制批次数为加载器中的批次数
    for i, (input_batch, target_batch) in enumerate(data_loader):  # 遍历加载器中的批次
        if i < num_batches:  # 如果未达到指定批次数
            loss = calc_loss_batch(input_batch, target_batch, model, device)  # 计算批次损失
            total_loss += loss.item()  # 累加损失
        else:
            break  # 超过指定批次数则退出
    return total_loss / num_batches  # 返回平均损失


def evaluate_model(model, train_loader, val_loader, device, eval_iter):  # 定义evaluate_model函数
    model.eval()  # 设置模型为评估模式
    with torch.no_grad():  # 禁用梯度跟踪
        train_loss = calc_loss_loader(train_loader, model, device,  # 计算训练集损失
                                      num_batches=eval_iter)
        val_loss = calc_loss_loader(val_loader, model, device,  # 计算验证集损失
                                    num_batches=eval_iter)
    model.train()  # 设置模型为训练模式
    return train_loss, val_loss  # 返回训练和验证损失


def generate_and_print_sample(model, tokenizer, device, start_context):  # 定义generate_and_print_sample函数
    model.eval()  # 设置模型为评估模式
    context_size = model.pos_emb.weight.shape[0]  # 获取上下文大小
    encoded = text_to_token_ids(start_context, tokenizer).to(device)  # 将文本转换为词元ID并移动到设备
    with torch.no_grad():  # 禁用梯度跟踪
        token_ids = generate_text_simple(  # 生成文本词元ID
            model=model, idx=encoded,
            max_new_tokens=50, context_size=context_size
        )
    decoded_text = token_ids_to_text(token_ids, tokenizer)  # 将词元ID转换为文本
    print(decoded_text.replace("\n", " "))  # 打印生成的文本，以紧凑格式显示
    model.train()  # 设置模型为训练模式


def train_model_simple(model, train_loader, val_loader, optimizer, device, num_epochs,  # 定义train_model_simple函数
                       eval_freq, eval_iter, start_context, tokenizer):
    train_losses, val_losses, track_tokens_seen = [], [], []  # 初始化列表以跟踪损失和看到的词元
    tokens_seen, global_step = 0, -1  # 初始化词元计数和全局步数

    for epoch in range(num_epochs):  # 开始主要训练循环
        model.train()  # 设置模型为训练模式
        for input_batch, target_batch in train_loader:  # 遍历训练数据
            optimizer.zero_grad()  # 重置前一批次迭代的梯度
            loss = calc_loss_batch(input_batch, target_batch, model, device)  # 计算批次损失
            loss.backward()  # 计算损失梯度
            optimizer.step()  # 使用损失梯度更新模型权重
            tokens_seen += input_batch.numel()  # 更新词元计数
            global_step += 1  # 增加全局步数

            if global_step % eval_freq == 0:  # 可选的评估步骤
                train_loss, val_loss = evaluate_model(  # 评估模型性能
                    model, train_loader, val_loader, device, eval_iter)
                train_losses.append(train_loss)  # 添加训练损失到列表
                val_losses.append(val_loss)  # 添加验证损失到列表
                track_tokens_seen.append(tokens_seen)  # 记录看到的词元数
                print(f"Ep {epoch + 1} (Step {global_step:06d}): "  # 打印当前训练信息
                      f"Train loss {train_loss:.3f}, Val loss {val_loss:.3f}")

    generate_and_print_sample(  # 生成并打印样本
        model, tokenizer, device, start_context
    )
    return train_losses, val_losses, track_tokens_seen  # 返回训练和验证损失及词元计数


def assign(left, right):  # 定义assign函数
    if left.shape != right.shape:  # 如果形状不匹配
        raise ValueError(f"Shape mismatch. Left: {left.shape}, Right: {right.shape}")  # 抛出错误
    return torch.nn.Parameter(torch.tensor(right))  # 返回右张量作为可训练参数


def load_weights_into_gpt(gpt, params):  # 定义load_weights_into_gpt函数
    gpt.pos_emb.weight = assign(gpt.pos_emb.weight, params["wpe"])  # A 分配位置嵌入权重
    gpt.tok_emb.weight = assign(gpt.tok_emb.weight, params["wte"])  # A 分配词元嵌入权重

    for b in range(len(params["blocks"])):  # B 遍历块
        q_w, k_w, v_w = np.split(  # C 分割权重
            params["blocks"][b]["attn"]["c_attn"]["w"], 3, axis=1)
        gpt.trf_blocks[b].attn.W_query.weight = assign(
            gpt.trf_blocks[b].attn.W_query.weight, q_w.T)
        gpt.trf_blocks[b].attn.W_key.weight = assign(
            gpt.trf_blocks[b].attn.W_key.weight, k_w.T)
        gpt.trf_blocks[b].attn.W_value.weight = assign(
            gpt.trf_blocks[b].attn.W_value.weight, v_w.T)

        q_b, k_b, v_b = np.split(
            params["blocks"][b]["attn"]["c_attn"]["b"], 3, axis=0)
        gpt.trf_blocks[b].attn.W_query.bias = assign(gpt.trf_blocks[b].attn.W_query.bias, q_b)
        gpt.trf_blocks[b].attn.W_key.bias = assign(gpt.trf_blocks[b].attn.W_key.bias, k_b)
        gpt.trf_blocks[b].attn.W_value.bias = assign(gpt.trf_blocks[b].attn.W_value.bias, v_b)

        gpt.trf_blocks[b].attn.out_proj.weight = assign(
            gpt.trf_blocks[b].attn.out_proj.weight,
            params["blocks"][b]["attn"]["c_proj"]["w"].T)
        gpt.trf_blocks[b].attn.out_proj.bias = assign(
            gpt.trf_blocks[b].attn.out_proj.bias,
            params["blocks"][b]["attn"]["c_proj"]["b"])

        gpt.trf_blocks[b].ff.layers[0].weight = assign(
            gpt.trf_blocks[b].ff.layers[0].weight, params["blocks"][b]["mlp"]["c_fc"]["w"].T)  # 分配MLP层权重
        gpt.trf_blocks[b].ff.layers[0].bias = assign(  # 分配MLP层偏置
            gpt.trf_blocks[b].ff.layers[0].bias,
            params["blocks"][b]["mlp"]["c_fc"]["b"])
        gpt.trf_blocks[b].ff.layers[2].weight = assign(  # 分配MLP层权重
            gpt.trf_blocks[b].ff.layers[2].weight,
            params["blocks"][b]["mlp"]["c_proj"]["w"].T)
        gpt.trf_blocks[b].ff.layers[2].bias = assign(  # 分配MLP层偏置
            gpt.trf_blocks[b].ff.layers[2].bias,
            params["blocks"][b]["mlp"]["c_proj"]["b"])

        gpt.trf_blocks[b].norm1.scale = assign(  # 分配第一个LayerNorm层权重
            gpt.trf_blocks[b].norm1.scale,
            params["blocks"][b]["ln_1"]["g"])
        gpt.trf_blocks[b].norm1.shift = assign(  # 分配第一个LayerNorm层偏置
            gpt.trf_blocks[b].norm1.shift,
            params["blocks"][b]["ln_1"]["b"])
        gpt.trf_blocks[b].norm2.scale = assign(  # 分配第二个LayerNorm层权重
            gpt.trf_blocks[b].norm2.scale,
            params["blocks"][b]["ln_2"]["g"])
        gpt.trf_blocks[b].norm2.shift = assign(  # 分配第二个LayerNorm层偏置
            gpt.trf_blocks[b].norm2.shift,
            params["blocks"][b]["ln_2"]["b"])

    gpt.final_norm.scale = assign(gpt.final_norm.scale, params["g"])  # 分配最终LayerNorm层权重
    gpt.final_norm.shift = assign(gpt.final_norm.shift, params["b"])  # 分配最终LayerNorm层偏置
    gpt.out_head.weight = assign(gpt.out_head.weight, params["wte"])  # D 将输出层权重与词元嵌入层权重绑定

class GPTDatasetV1(Dataset):  # 定义GPTDatasetV1类，继承自Dataset
    def __init__(self, txt, tokenizer, max_length, stride):  # 初始化方法
        self.input_ids = []  # 初始化输入ID列表
        self.target_ids = []  # 初始化目标ID列表

        token_ids = tokenizer.encode(txt)  # 对文本进行分词

        for i in range(0, len(token_ids) - max_length, stride):  # 使用滑动窗口将文本分块
            input_chunk = token_ids[i:i + max_length]  # 获取输入分块
            target_chunk = token_ids[i + 1: i + max_length + 1]  # 获取目标分块
            self.input_ids.append(torch.tensor(input_chunk))  # 将输入分块转换为张量并添加到列表中
            self.target_ids.append(torch.tensor(target_chunk))  # 将目标分块转换为张量并添加到列表中

    def __len__(self):  # 返回数据集的长度
        return len(self.input_ids)  # 返回输入ID的长度

    def __getitem__(self, idx):  # 获取指定索引的数据
        return self.input_ids[idx], self.target_ids[idx]  # 返回输入和目标的张量

def create_dataloader_v1(txt, batch_size=4, max_length=256,  # 创建数据加载器
                         stride=128, shuffle=True, drop_last=True, num_workers=0):
    tokenizer = tiktoken.get_encoding("gpt2")  # 初始化分词器
    dataset = GPTDatasetV1(txt, tokenizer, max_length, stride)  # 创建数据集
    dataloader = DataLoader(  # 创建数据加载器
        dataset,
        batch_size=batch_size,
        shuffle=shuffle,
        drop_last=drop_last,  # 如果最后一个批次小于指定批次大小，则丢弃
        num_workers=num_workers  # 使用的CPU进程数量
    )
    return dataloader  # 返回数据加载器

def plot_losses(epochs_seen, tokens_seen, train_losses, val_losses):  # 定义plot_losses函数
    fig, ax1 = plt.subplots(figsize=(5, 3))  # 创建一个图形和一个子图，图形大小为5x3
    ax1.plot(epochs_seen, train_losses, label="Training loss")  # 在第一个子图上绘制训练损失
    ax1.plot(epochs_seen, val_losses, linestyle="-.", label="Validation loss")  # 在第一个子图上绘制验证损失，使用点线样式
    ax1.set_xlabel("Epochs")  # 设置x轴标签为“Epochs”
    ax1.set_ylabel("Loss")  # 设置y轴标签为“Loss”
    ax1.legend(loc="upper right")  # 设置图例位置为右上角
    ax2 = ax1.twinx()  # 创建共享同一y轴的第二个x轴  #A
    ax2.plot(tokens_seen, train_losses, alpha=0)  # 对齐刻度的隐形图  #B
    ax2.set_xlabel("Tokens seen")  # 设置第二个x轴标签为“Tokens seen”
    fig.tight_layout()  # 自动调整子图参数以填充整个图形区域
    plt.show()  # 显示图形

# 定义一个名为LoRALayer的类，继承自torch.nn.Module
class LoRALayer(torch.nn.Module):
    def __init__(self, in_dim, out_dim, rank, alpha):
        """
        初始化LoRALayer层

        参数：
        in_dim: 输入维度
        out_dim: 输出维度
        rank: 低秩矩阵的秩
        alpha: 缩放因子
        """
        super().__init__()  # 调用父类的初始化方法

        # 定义低秩矩阵A，初始化为大小为(in_dim, rank)的空矩阵
        self.A = torch.nn.Parameter(torch.empty(in_dim, rank))
        # 使用Kaiming均匀初始化矩阵A
        torch.nn.init.kaiming_uniform_(self.A, a=math.sqrt(5))

        # 定义低秩矩阵B，初始化为大小为(rank, out_dim)的零矩阵
        self.B = torch.nn.Parameter(torch.zeros(rank, out_dim))

        # 保存alpha参数
        self.alpha = alpha

    def forward(self, x):
        """
        前向传播方法

        参数：
        x: 输入张量

        返回：
        x: 输出张量
        """
        # 通过公式：x = alpha * (x @ A @ B) 计算输出
        x = self.alpha * (x @ self.A @ self.B)
        return x  # 返回结果

class LinearWithLoRA(torch.nn.Module):
    def __init__(self, linear, rank, alpha):
        """
        初始化 LinearWithLoRA 层

        参数：
        linear: 一个预先定义的线性层 (torch.nn.Linear)
        rank: LoRA 低秩矩阵的秩
        alpha: LoRA 的缩放因子
        """
        super().__init__()  # 调用父类的初始化方法

        self.linear = linear  # 保存传入的线性层
        self.lora = LoRALayer(
            linear.in_features,  # 输入特征维度
            linear.out_features,  # 输出特征维度
            rank,  # LoRA 的秩
            alpha  # LoRA 的缩放因子
        )

    def forward(self, x):
        """
        前向传播

        参数：
        x: 输入张量

        返回：
        线性层输出和 LoRA 层输出之和
        """
        return self.linear(x) + self.lora(x)  # 将原始线性层输出与LoRA层的输出相加

def replace_linear_with_lora(model, rank, alpha):
    """
    将模型中的所有Linear层替换为LinearWithLoRA层

    参数：
    model: 要修改的PyTorch模型
    rank: LoRA层的秩
    alpha: LoRA层的缩放因子
    """
    # 遍历模型中的所有子模块
    for name, module in model.named_children():
        # 如果模块是torch.nn.Linear类型
        if isinstance(module, torch.nn.Linear):
            # 使用LinearWithLoRA替换该模块
            setattr(model, name, LinearWithLoRA(module, rank, alpha))
        else:
            # 递归调用本函数
            replace_linear_with_lora(module, rank, alpha)



